package com.my.thread.transaction.service;

import com.my.db.test.mybatis.Employee;
import com.my.db.test.mybatis.mapper.EmployeeMapper;
import com.my.thread.transaction.TaskError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

@Service
public class TestTranService {
    @Autowired
    private EmployeeMapper employeeMapper;

    //多数据源情况下需要进行额外配置 PlatformTransactionManager
//    @Autowired
//    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    private PlatformTransactionManager dataSourceTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;
    //超过线程数会报错
    //错误的大致意思时，不能为数据库事务打开 jdbc Connection，连接在 30s 的时候超时了。
    //由于前面启动的十个线程需要等待主线程完成后才能提交，所以一直占用连接未释放，造成后面的进程创建连接超时。
    //看错误日志中错误的来源是 HikariPool ，我们来重新配置一下这个连接池的参数，将最大连接数修改为 100。

    //# 连接池中允许的最小连接数。缺省值：10
    //spring.datasource.hikari.minimum-idle=10
    //# 连接池中允许的最大连接数。缺省值：10
    //spring.datasource.hikari.maximum-pool-size=100
    //# 自动提交
    //spring.datasource.hikari.auto-commit=true
    //# 一个连接idle状态的最大时长（毫秒），超时则被释放（retired），缺省:10分钟
    //spring.datasource.hikari.idle-timeout=30000
    //# 一个连接的生命时长（毫秒），超时而且没被使用则被释放（retired），缺省:30分钟，建议设置比数据库超时时长少30秒
    //spring.datasource.hikari.max-lifetime=1800000
    //# 等待连接池分配连接的最大时长（毫秒），超过这个时长还没可用的连接则发生SQLException， 缺省:30秒
    public void update(List<Employee> employeeList,
                       CountDownLatch threadLatch,
                       CountDownLatch mainLatch,
                       TaskError taskStatus) {
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        System.out.println("子线程：" + Thread.currentThread().getName());
        try {
            employeeList.forEach(s -> {
                String teacher = s.getName();
                String newName = "TNO_" + new Random().nextInt(100);
                s.setName(newName);
                employeeMapper.updateById(s);
            });
        } catch (Throwable e) {
            taskStatus.setIsError(true);
        } finally {
            threadLatch.countDown(); // 切换到主线程执行
        }
        try {
            mainLatch.await();  //等待主线程执行
        } catch (Throwable e) {
            taskStatus.setIsError(true);
        }
        // 判断是否有错误，如有错误 就回滚事务
        if (taskStatus.getIsError()) {
            dataSourceTransactionManager.rollback(transactionStatus);
        } else {
            dataSourceTransactionManager.commit(transactionStatus);
        }
    }

    //推荐用法
    //事务集合来进行多线程事务控制
    @Transactional(propagation = Propagation.REQUIRED,
//            transactionManager = "platformTransactionManager",
            rollbackFor = {Exception.class})
    public void updateStudentsTransaction(PlatformTransactionManager transactionManager,
                                          List<TransactionStatus> transactionStatuses,
                                          List<Employee> employees) {
        // 使用这种方式将事务状态都放在同一个事务里面
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别，开启新事务，这样会比较安全些。
        TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态
        transactionStatuses.add(status);

        employees.forEach(s -> {
            String teacher = s.getName();
            String newName = "TNO_" + new Random().nextInt(100);
            s.setName(newName);
            employeeMapper.updateById(s);
        });
        System.out.println("子线程：" + Thread.currentThread().getName());
    }
}
